import subprocess as sp
import re

def main():
    from pprint import pprint
    
    p = PyFi("eth0")
    pprint(p.scan())

class PyFi(object):
    """
    A wrapper to the unix 'iw*' family of wireless network tools.
    """
    
    def __init__(self, interface):
        """
        Initialize the object to a specific interface on this machine.
        Throws a ValueError if the interface doesn't exist.
        """
        
        # deal with the interface not existing
        if not self._interface_exists(interface):
            raise ValueError("Interface does not exist: " + interface)
        
        self._interface = interface
    
    def set_interface(self, interface):
        """
        Reassign the interface we deal with.  Throws a ValueError if the
        interface doesn't exist.
        """
        
        # deal with the itnerface not existing
        if not self._interface_exists(interface):
            raise ValueError("Interface does not exist: " + interface)
        
        self._interface = interface
    
    def _interface_exists(self, interface):
        """
        Does a given interface exist on this computer?
        """
        
        # get the process's return value, namely a list of the interfaces
        args = ["ifconfig", "-a", "-s"]
        ifconfig = sp.Popen(args, stdout = sp.PIPE, stderr = sp.STDOUT)
        
        result = ifconfig.communicate()[0] # the text the process returned
        
        # each unique result has at least one space after its name
        return result.count(interface + " ") > 0

    def scan(self):
        """
        Return a list of networks (as dictionaries) found after a scan.
        """
        
        # perform the system call and get the return text
        args = ["iwlist", self._interface, "scan"]
        iwlist = sp.Popen(args, stdout = sp.PIPE, stderr = sp.STDOUT)
        result_text = iwlist.communicate()[0] # text process returned
        
#         # TEST SCAN RESULT
#         with open("scan.txt") as scan_txt:
#             result_text = scan_txt.read()
        
        # parse the scan results for return
        scan_results = self._parse_scan_results(result_text)
        return scan_results

    def _parse_scan_results(self, scan_text):
        """
        Parse the text results a call to 'iwscan' returned.
        """
        
        # deal with unscannable interface and/or no networks found
        if scan_text.count("Interface doesn't support scanning.") > 0:
            return []
        
        # we split by the line that says "Cell XX - Address:" to get each
        # we leave off the first element, the interface we scanned from
        cell_texts = re.split("\sCell \d+\s-\sAddress:\s*", scan_text)[1:]
        
        # make each cell into a dict and store in a list we'll return
        cell_list = []
        for cell in cell_texts:
            # get router mac address
            address = re.findall("((?:[A-F\d]{2}:)+[A-F\d]{2})\s*", cell)
            address = None if len(address) == 0 else address[0]
            
            # get network name
            essid = re.findall('\s+ESSID:"([^"\r\n]*)\s*"', cell)
            essid = None if len(essid) == 0 else essid[0]
            
            # get network mode
            mode = re.findall("\s+Mode:([^\r\n]*)\s*", cell)
            mode = None if len(mode) == 0 else mode[0]
            
            # get channel
            channel = re.findall("\s+Channel:([^\s]*)\s*", cell)
            channel = None if len(channel) == 0 else int( channel[0] )
            
            # get signal data into its own dict
            # start with quality
            q = re.findall("\s+Quality:(\d+)/(\d+)\s+", cell)
            # turn quality into a float rather than a string
            q = None if len(q) == 0 else float(q[0][0]) / float(q[0][1])
            
            # get signal level
            level = re.findall("\s+Signal level:(-?\d+)\s+dBm\s+", cell)
            level = None if len(level) == 0 else int(level[0])
            
            # get noise level
            noise = re.findall("\s+Noise level:(-?\d+)\s+dBm\s*", cell)
            noise = None if len(noise) == 0 else int(noise[0])
            
            # make the signal a dict for intuitive access
            signal = {"quality" : q, "level" : level, "noise" : noise}
            
            # get encryption status
            encrypt = re.findall("\s+Encryption key:(\w+)\s*", cell)
            encrypt = None if len(encrypt) == 0 else encrypt[0] == "on"
            
            # get network bit rate
            rate = re.findall("\s+Bit Rates:(\d+)\sMb/s\s*", cell)
            rate = None if len(rate) == 0 else int(rate[0])
            
            # add this cell as a dict for intuitive access
            cell_list.append( { "address" : address,
                                "name" : essid,
                                "mode" : mode,
                                "channel" : channel,
                                "signal" : signal,
                                "encrypted" : encrypt,
                                "rate" : rate } )
        
        return cell_list

if __name__ == "__main__":
    main()
